using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Text;
using ParserGenerator.Output;

namespace ParserGenerator.CharacterMapping
{
    [Serializable]
    public sealed class CharMap
    {
        public CharInterval Universal { get; internal set; }
        public ReadOnlyCollection<CharClass> Classes { get; internal set; }
        public ReadOnlyDictionary<CharClass, ReadOnlyCollection<int>> Map { get; internal set; }
        public ReadOnlyCollection<int> FastArray { get; internal set; }
        public ReadOnlyCollection<int> FirstArray { get; internal set; }
        public ReadOnlyCollection<int> LastArray { get; internal set; }
        public ReadOnlyCollection<int> MapArray { get; internal set; }

        public int this[int c]
        {
            get
            {
                if (!Universal.Contains(c))
                    throw new ArgumentOutOfRangeException(nameof(c), string.Format(SR.CharMustBeInRange, Universal));
                if (FastArray != null && 0 <= c && c < FastArray.Count)
                    return FastArray[c];
                int i = 0;
                int j = MapArray.Count - 1;
                while (i <= j)
                {
                    int k = i + (j - i) / 2;
                    if (c < FirstArray[k])
                        j = k - 1;
                    else if (LastArray[k] < c)
                        i = k + 1;
                    else
                        return MapArray[k];
                }
                throw new ArgumentOutOfRangeException(nameof(c), SR.CharNotFoundInMap);
            }
        }

        internal CharMap()
        {
        }

        public void Jsonize(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            writer.OpenCurlyBlock();
            writer.WriteLine("\"universal\": [{0},{1}],", Universal.First, Universal.Last);
            writer.WriteLine("\"classes\": [");
            writer.OpenBlock();
            for (int i = 0; i < Classes.Count; i++)
            {
                JsonizeCharClass(writer, Classes[i]);
                writer.WriteLine(i == Classes.Count - 1 ? "]," : ",");
            }
            writer.CloseBlock();
            writer.WriteLine("\"map\": [");
            writer.OpenBlock();
            int index = 0;
            foreach (var t in Map)
            {
                if (index++ > 0)
                    writer.WriteLine(", ");
                writer.Write("{ \"key\": ");
                JsonizeCharClass(writer, t.Key);
                writer.Write(", \"value\": ");
                JsonizeIntArray(writer, t.Value);
                writer.Write(" }");
            }
            writer.WriteLine("],");
            writer.CloseBlock();
            writer.Write("\"fastArray\": ");
            if (FastArray == null)
                writer.Write("null");
            else
                JsonizeIntArray(writer, FastArray);
            writer.WriteLine(",");
            writer.Write("\"firstArray\": ");
            JsonizeIntArray(writer, FirstArray);
            writer.WriteLine(",");
            writer.Write("\"lastArray\": ");
            JsonizeIntArray(writer, LastArray);
            writer.WriteLine(",");
            writer.Write("\"mapArray\": ");
            JsonizeIntArray(writer, MapArray);
            writer.WriteLine();
            writer.CloseCurlyBlock();
        }

        private void JsonizeCharClass(IndentedWriter writer, CharClass value)
        {
            writer.Write("[");
            for (int i = 0; i < value.Intervals.Count; i++)
            {
                if (i > 0)
                    writer.Write(", ");
                CharInterval item = value.Intervals[i];
                writer.Write("[{0}, {1}]", item.First, item.Last);
            }
            writer.Write("]");
        }

        private void JsonizeIntArray(IndentedWriter writer, ReadOnlyCollection<int> items)
        {
            writer.Write("[");
            for (int i = 0; i < items.Count; i++)
            {
                if (i > 0)
                    writer.Write(", ");
                writer.Write(items[i]);
            }
            writer.Write("]");
        }

        public string ToJsonString()
        {
            using (StringWriter stream = new StringWriter())
            {
                IndentedWriter writer = new IndentedWriter(stream);
                stream.NewLine = "\n";
                Jsonize(writer);
                return stream.ToString().Trim();
            }
        }

        public sealed override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < Classes.Count; i++)
                sb.AppendFormat("{0}\n", Classes[i]);
            foreach (var t in Map)
            {
                sb.AppendFormat("\n{0} =>", t.Key);
                foreach (int m in t.Value)
                    sb.AppendFormat(" {0}", m);
            }
            return sb.ToString();
        }
    }
}
